// Changement : on exporte que les donnees, c'est povray qui fabriquera ses objets
// On ne dessine qu'au niveau inferieur
// On part de trois cercles tangents, on construit le gasket
// On veut pouvoir remplacer un cercle par une droite


import ij.ImagePlus;
import ij.plugin.PlugIn;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;


import java.awt.Color;


import java.awt.Font;

import java.awt.Polygon;
import java.io.PrintStream;

import java.util.ArrayList;
import java.util.Locale;
import java.util.Random; 
import java.util.TreeSet;



import pluginUtils.AGCircle;
import pluginUtils.Circle;
import pluginUtils.CircleComparator;
import pluginUtils.CircleInversion;
import pluginUtils.Complexe; 
import pluginUtils.MobiusTransform;
import pluginUtils.Point2D;
import pluginUtils.SoddyTriplet;



public class GeneralApollonianGasket extends ImagePlus implements PlugIn{
	{Locale.setDefault(Locale.US); }
	
	private ImageProcessor primeIP; 
	private int X=10000,Y=10000; // image size
	private int maxDepth=40; 
	
	private PrintStream output;
	private double xShift=0; 
	private double zShift=0; 
	
	//private TreeSet<AGCircle> enAttente=new TreeSet<AGCircle>(new CircleComparator()); 
	private double decale=4.5; 
	ArrayList<AGCircle>cerclesConnus; 
	
	private double minRad=0.00006125; //0.0003125; 
	private int compte=0,oldcompte=0; 
	

	private Random generator=new Random(); // One always needs some randomness
	
	// For the scaling of the image
	private double x0=0,x1=1,y0=0,y1=1;
	private double acoef,ccoef,vcoef,wcoef; 
	private double rayon=1.5; // radius of big circle
	private int level=0; 
	
	
	public void setCoefs(){
		acoef=X/(x1-x0); 
		vcoef=Y/(y1-y0); 
		ccoef=-acoef*x0; 
		wcoef=-vcoef*y0; 
	}
	 
	
	
	public void setBounds(double xl,double xu,double yl,double yu){
		x0=xl; 
		x1=xu;
		y0=yl;
		y1=yu; 
	}
	
	
	
	private void drawCircle(AGCircle c){
		double xCenter=c.getCenter().getReal(); 
		double yCenter=c.getCenter().getIm(); 
		//if(c.getRadius()<0.001) return ; 
		
		int intX=(int)((xCenter-xShift)*acoef+ccoef);
		int intY=(int)((yCenter-zShift)*vcoef+wcoef);
		int intRx=(int)(c.getRadius()*acoef);
		int intRy=(int)(c.getRadius()*vcoef);
		primeIP.drawOval(intX-intRx,intY-intRy,intRx*2,intRy*2);
	}
		

	/** On choisit un cercle, on y place trois cercles, et on lance l'algo **/
	private void makeGasket(AGCircle origin,int nb){
		double cx=origin.getXcenter()-decale; 
		double cy=origin.getYcenter()-decale;
		int nb0=nb; 
		
		
			
		AGCircle newOrigin=new AGCircle(origin.getCenter(),origin.getRadius(),true,level);
		double dx=newOrigin.getXcenter(); 
		double dy=newOrigin.getYcenter(); 
		AGCircle ring[]=new AGCircle[nb0];
		double petitr=newOrigin.getRadius()*(Math.sin(Math.PI/nb0)/(1+Math.sin(Math.PI/nb0)));
		
		for(int i=0;i<nb0;i++){
			ring[i]=new AGCircle(Complexe.createComplexeFromRI(dx+(newOrigin.getRadius()-petitr)*Math.cos(2*Math.PI*i/nb0),dy+(newOrigin.getRadius()-petitr)*Math.sin(2*Math.PI*i/nb0 )), petitr,false,level);
			
			if(ring[i].getRadius()>=minRad){
			cerclesConnus.add(ring[i]); 
			
			
			//drawCircle(ring[i]);
			compte++; 
		
			
			}
		}
		AGCircle middle=new AGCircle(Complexe.createComplexeFromRI(dx, dy),newOrigin.getRadius()-2*petitr,false,level);
		if(middle.getRadius()>=minRad)
		{
			cerclesConnus.add(middle); 
		
		    //drawCircle(middle);
		    compte++; 
		 
		}
		ArrayList<SoddyTriplet> myLocalSoddyTriplets=new ArrayList<SoddyTriplet>(); 
		
		for(int i=0;i<nb0;i++){
			myLocalSoddyTriplets.add(new SoddyTriplet(newOrigin, ring[i], ring[(i+1)%nb0])); 
			myLocalSoddyTriplets.add(new SoddyTriplet(ring[i], ring[(i+1)%nb0], middle));
		}
		
		while(myLocalSoddyTriplets.size()!=0){
		ArrayList<SoddyTriplet> pro=new ArrayList<SoddyTriplet>();
		for(SoddyTriplet toto:myLocalSoddyTriplets){
			AGCircle nouvo=toto.getMini();
			if((nouvo.getRadius()>=minRad)&&(nouvo.getRadius()<3)){
			
				
				//drawCircle(nouvo);
				compte++; 
				if(compte-oldcompte>=10000)
			
				{ 
				updateAndDraw();
				oldcompte=compte; 
				System.out.println("** "+cerclesConnus.size());  
				}
			
				
				cerclesConnus.add(nouvo); 
				AGCircle a=toto.getA(); 
				AGCircle b=toto.getB(); 
				AGCircle c=toto.getC();

			    pro.add(new SoddyTriplet(a,b,nouvo)); 
				pro.add(new SoddyTriplet(a,c,nouvo));
			    pro.add(new SoddyTriplet(c,b,nouvo));		
			} //if
			
		}
		myLocalSoddyTriplets=pro; 
		//System.out.println(pro.size());
		} // while
	}
	
	
	
	
	
	public void run(String arg) {
		primeIP = new ColorProcessor(X,Y);
		
		// T=kz
		Complexe aComplex=Complexe.createComplexeFromRI(0,1);
		Complexe bComplex=Complexe.createComplexeFromRI(1,0); 
		Complexe c=Complexe.createComplexeFromRI(1,0);
		Complexe d=Complexe.createComplexeFromRI(0,1); 
		//Complexe d=Complexe.createComplexeFromRI(0,1); 
		MobiusTransform T=new MobiusTransform(aComplex,bComplex,c,d); 
		System.out.println("***\n"+T+"\n***");
		System.out.println("***\n"+T.inverse()+"\n***");
		
		// Conjugating with R
		Complexe ar=Complexe.createComplexeFromRI(0,1);
		Complexe br=Complexe.createComplexeFromRI(1,0); 
		Complexe cr=Complexe.createComplexeFromRI(1,0);
		Complexe dr=Complexe.createComplexeFromRI(Math.sqrt(2),1); 
		MobiusTransform R=new MobiusTransform(ar,br,cr,dr);
		MobiusTransform Rinv=R.inverse(); 
		//System.out.println("***\n"+R+"\n Inverse :\n"+Rinv);
		
		
		
		
		
		MobiusTransform conj=Rinv.mul(T.mul(R)); 
		
		
		
	
		double limit=6.1; 
		double lmoins=2.9;
		
		//setBounds(0.15,0.19,0.80,0.84);
		setBounds(lmoins,limit,lmoins,limit);
		//setBounds(-0.25,0.5,-0.25,0.5); 
		setCoefs(); 
	
		Polygon background=new Polygon(new int[]{0,0,X,Y},new int[]{0,X,Y,0},4);
		primeIP.setColor(Color.WHITE);
		
		primeIP.fillPolygon(background);
		
		
		primeIP.setFont(new Font(null,Font.ITALIC,60));
		primeIP.setColor(Color.BLACK);
		primeIP.setLineWidth(1); 
		

		setProcessor("Verifying", primeIP);
		
		primeIP.setLineWidth(1);
	
		show();
		
		cerclesConnus=new ArrayList<AGCircle>();
		
		//AGCircle init1=new AGCircle(Complexe.createComplexeFromRI(decale,decale+2/3.0*Math.sqrt(3)),1,false,0); 
		AGCircle init1=new AGCircle(Complexe.createComplexeFromRI(decale,decale+10000),10000,false,0); 
		AGCircle init2=new AGCircle(Complexe.createComplexeFromRI(decale+1,decale-Math.sqrt(3)/3),1,false,0); 
		AGCircle init3=new AGCircle(Complexe.createComplexeFromRI(decale-1,decale-Math.sqrt(3)/3),1,false,0); 
		
		drawCircle(init1); 
		drawCircle(init2); 
		drawCircle(init3); 
	
		ArrayList<SoddyTriplet> myLocalSoddyTriplets=new ArrayList<SoddyTriplet>(); 
		
		myLocalSoddyTriplets.add(new SoddyTriplet(init1, init2, init3)); 
		while(myLocalSoddyTriplets.size()!=0){
			ArrayList<SoddyTriplet> pro=new ArrayList<SoddyTriplet>();
			for(SoddyTriplet toto:myLocalSoddyTriplets){
				AGCircle nouvo=toto.getMini();
				if((nouvo.getRadius()>=minRad)&&(nouvo.getRadius()<3)){
				
					
					//drawCircle(nouvo);
					compte++; 
					if(compte-oldcompte>=10000)
				
					{ 
					updateAndDraw();
					oldcompte=compte; 
					System.out.println("** "+cerclesConnus.size());  
					}
				
					
					cerclesConnus.add(nouvo); 
					AGCircle a=toto.getA(); 
					AGCircle b=toto.getB(); 
					AGCircle cc=toto.getC();

				    pro.add(new SoddyTriplet(a,b,nouvo)); 
					pro.add(new SoddyTriplet(a,cc,nouvo));
				    pro.add(new SoddyTriplet(cc,b,nouvo));		
				} //if
				
			}
			myLocalSoddyTriplets=pro; 
			//System.out.println(pro.size());
			} // while
		
		try{
			output=new PrintStream("F:/Povray/outApolloGasket.txt");
			//output=new PrintStream("C:/users/decomite/pictures/outApolloGasket.txt"); 
			}
			catch(Exception e){System.out.println("Probleme 2 "+e); System.exit(0);}
		double ymin=1000; 
		
		for(AGCircle v:cerclesConnus) {
			drawCircle(v);
			System.out.println(v); 
			output.print((v.getXcenter()-decale)+","+(v.getYcenter()-decale)+","+v.getRadius()+","+v.getColor()+",");
		 double empan=v.getYcenter()-decale-v.getRadius(); 
			if(empan<ymin) {ymin=empan;}
			
		}
		output.close();
		updateAndDraw(); 
		System.out.println("fini "+compte+" "+ymin); 
		
	}	
}


